MEGAのクラウドデータをシンクロする

バックアップを取っていなかったために悲惨な経験を何度もしている私は、なんとしてもMEGAのバックアップを取ろうとしたのですが、どうもLinuxのツールでもはうまく行かないようです。リポジトリにMegatoolsというのがありますが、クラウドからファイルコピー取りこぼしが多く、あまり使えません。またrcloneでは認証方法が変わったせいかログインすらできません。いろいろ調べてみると、MEGAcmdというツールがあったので試してみました。

NASサーバでコンパイル、インストールして使ってみます。helpでコマンド一覧がでますが、サイトのチュートリアルにあるようにsyncコマンドが実用的だと思われます。コマンドを使う方法はインターラクティブモード、ノンインターラクティブモードがあります。インタラクティブモードではmegacliでシェルを起動してからメールログインします。helpでコマンドの一覧を取得できます。シンクするにはNASに空のフォルダー/path/to/local/folderを用意してログインします。

​sync /path/to/local/folder /

 

これですべてをシンクできます。経過を見ているとどうもhttp接続のエラーが頻発したりPPPoE接続が途切れてアドレスが変わったりしましたが、繰り返しコマンドを実行したら無事シンクロができてたようです。

Marvel wifiモジュールの不具合

最近のSamsung ChromeBookのArchlinuxのアップデートでWifiの問題が発生したので調べてみました。

journalctl -xf

すると、次のようなエラーがありました。

...
WLAN FW is active
CMD_RESP: cmd 0x242 error, result=0x2
...

調べてみると過去数年に渡ってこの問題が繰り返しおこっており、カーネルを正常に動作していた前バージョンに戻すよりほかはありません。

Archlinuxはローリング・リリースを採用していて、過去のパッケージは残らないようになっているので注意が必要です。/var/cache/pacman/pkgにダウンロードしたパッケージが置かれており、それを使ってダウングレードします。

linux-armv7-5.2.10-2-armv7h.pkg.tar.xz
linux-armv7-chromebook-5.2.10-2-armv7h.pkg.tar.xz

pacman -U <pkg name>でダウングレードします。

カーネルのinitramfsもアップデートする必要があります。

mkinitcpio -k 5.2.10-2-ARCH -g /boot/initramfs-linux.img
==> Starting build: 5.2.10-2-ARCH
  -> Running build hook: [base]
  -> Running build hook: [udev]
  -> Running build hook: [autodetect]
  -> Running build hook: [keyboard]
  -> Running build hook: [keymap]
  -> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
  -> Running build hook: [modconf]
  -> Running build hook: [block]
  -> Running build hook: [encrypt]
  -> Running build hook: [filesystems]
  -> Running build hook: [fsck]
==> Generating module dependencies
==> Creating gzip-compressed initcpio image: /boot/initramfs-linux.img
==> Image generation successful

これでカーネルパッケージのダウングレードは完了します。

pacmanの設定

ダウングレードができたらカーネルを更新しないように設定します。

/etc/pacman.conf

IgnorePkg   = linux-armv7 linux-armv7-chromebook

shorewallのLogging

shorewallは複雑なFirewallの設定ができるので重宝しますが、なぜかLogメッセージが/var/log/messageと/var/log/syslogの両方に書き込みsyslogが見づらくなるので修正してみました。shorewallの新旧バージョンによって設定が異なるのようなので整理しました。shorewall.confでログ先を変更しても反映されないのでrsyslog側でメッセージを振り分けます。

Shorewall version 5.0.15.6 [Debian GNU/Linux 9]

/etc/shorewall/shorewall.conf

LOGFILE=/var/log/messages

/etc/rsyslog.d/shorewall.conf

:msg, contains, "Shorewall:" /var/log/shorewall.log
& stop

/etc/logrotate.d/shorewall

/var/log/shorewall-init.log {
    weekly
    rotate 4
    compress
    missingok
    create 0640 root adm
}

/var/log/shorewall {
    weekly
    rotate 52
    compress
    compressext .gz
    missingok
    create 0640 root adm
}

Shorewall version 5.2.3.2 [Debian GNU/Linux 10]

/etc/rsyslog.d/shorewall.conf

:msg, contains, "net-fw" /var/log/shorewall.log
& stop

/etc/logrotate.d/shorewallは上記に同じ

 

PythonのLogging

単純なログ出力であればとくに書く必要もありませんが、少し複雑になるだけで、チュートリアルを読まないとわかないくらい煩雑です。しかも読んだだけだと今ひとつわからないというのがPythonのロギングです。

目的はBotのログ出力を、Bot自体の生死用、取引用と2つないし3つのログを取るためです。不具合を修正したり、取引内容を解析したりするのに、つねに張りついて監視しているわけではないので、ログだけが頼りです。それなので解析しやすいログ取りは必須条件です。

とりあえずloggingモジュールの使い方を検索して調べ、寄せ集めで作りました。これを基本にBotに組み込めば良いはずです。Botはいくつかパーツに分けてあるため定義したログオブジェクトを渡せばプログラム全体通してログがとれます。このテストプログラムではtest_log(super_logger)としてあります。

import logging
import time, calendar
import pytz
from datetime import datetime, date, tzinfo
from logger3 import test_log

timezone = 'Asia/Tokyo'

def customTime(*args):
    utc_dt = pytz.utc.localize(datetime.utcnow())
    tz = pytz.timezone(timezone)
    converted = utc_dt.astimezone(tz)
    return converted.timetuple()

logging.Formatter.converter = customTime
#formatter = logging.Formatter(fmt='%(asctime)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
formatter = logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')

def setup_logger(name, log_file, level=logging.INFO):
    handler = logging.FileHandler(log_file)   
    handler.setFormatter(formatter)
    logger = logging.getLogger(name)
    logger.setLevel(level)
    logger.addHandler(handler)

    return logger

# first file logger
logger = setup_logger('first_logger', 'first_logfile.log')
logger.info('This is just info message')

# second file logger
super_logger = setup_logger('second_logger', 'second_logfile.log')
super_logger.error('This is an error message')

test_log(super_logger)

def another_method():
   # using logger defined above also works here
   logger.info('Inside method')

Pythonの日時の扱い

Pythonで非常に悩ましいのは日時の記述方法の手段が多すぎてどれを使ったらよいのか迷うことです。当初は次のように使っていました。

import os, time
from datetime import datetime
os.environ['TZ'] = 'Asia/Tokyo'
time.tzset()
print(datetime.now())

これでも良かったのですが、いろいろ他者さんのコードを眺めているうちに、これだと環境によって問題が起こることがわかってきました。とくに旧型のWindowsはLocal時間をベースにしているので、問題が起こりそうなことは薄々分かっていました。なにぶん私の環境がLinuxなので問題はないのですが、コードがプラットフォームごとで異なる振る舞いをするのはあまり気持ちがいいものでもないので、一旦、Pythonの日時関係を整理してみました。ちなみにWindowsをUTCベースにするにはつぎのようにします。

reg add "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation" /v RealTimeIsUniversal /d 1 /t REG_DWORD /f

Pythonの日時データを扱う上で大事なことはつぎの3つです。

UNIX time: エポック秒: 1970/1/1 00:00:00
naive: TZを扱わない => timezone なし
aware: TZを扱う    => timezone あり

UNIX timeはUTCを基準として1970年のはじめから連続した数字です。それなのでUnix Timeをベースにして時間の前後を計算するのには適しています。しかし見やすいように表示となると変換が必要になります。
datetimeのnaiveとawareですが、とくに指定がなければnaiveになります。UTC時間とLOCAL時間の混同を避けるためにも、Timezoneで明示しておくのは良い考えでしょう。awareでTimezoneを扱う方法はいくつかあり、どれがよいのか調べてみました。

Pythonの日時データ型は次のとおりです。

string: 日時の文字列(扱いが少ない)
e.g)
'13:07:29'
'2019-09-21'

int: 数字
e.g)
2019, 9, 21

unixtime: int(secs) or float(secs)
e.g)
1569057147.0

datetime: datetime型
e.g)
datetime(2019, 9, 21, 6, 6, 6, 6, tzinfo=jst)

struct_time(named tuple): strptime型、名前付きタプル
e.g)
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=21, tm_hour=8, tm_min=46, tm_sec=18, tm_wday=5, tm_yday=264, tm_isdst=0)

サブクラスオブジェクト呼び出すときの注意

datetimeモジュールのサブクラスの中には、他のモジュールと競合するものがあるので呼び出すときは注意が必要です。

  • time:timeモジュールと競合
  • timezone:pytzのサブクラスオブジェクトと競合
from datetime import datetime, date
from pytz import timezone
import pytz
jst = timezone('Asia/Tokyo')
datetime(2019, 9, 21, 6, 6, 6, 6, tzinfo=jst)

unixtime

Unixtimeを求めるときは、time.time()で呼び出すのが簡単だと思いますが、プラットフォームにより違いがあるらしいので次の書式が定形式になっているようです。

import calendar
from datetime import datetime
now = calendar.timegm(datetime.utcnow().utctimetuple())

datetime型のデータなので次のように日時を入力すれば、Unixtimeが求められます。

>>> calendar.timegm(datetime(2019,9,22,8,10,59).utctimetuple())
1569139859

LocaltimeでUnixtimeを求める場合です。

誤)

>>> import calendar
>>> from datetime import datetime
>>> from pytz import timezone
>>> jst = timezone('Asia/Tokyo')
>>> calendar.timegm(datetime(2019,9,22,8,10,59, tzinfo=jst).utctimetuple())
1569106319

(1569139859 – 1569106319) / 60 / 60 = 9.316666667

正)

>>> import calendar
>>> from datetime import datetime, timedelta, timezone
>>> jst = timezone(timedelta(hours=+9), 'JST')
>>> calendar.timegm(datetime(2019,9,22,8,10,59, tzinfo=jst).utctimetuple())
1569107459

(1569139859 – 1569107459) / 60 / 60 = 9

注意:datetime.astimezone()だと正確なUnixtimeが求められません。

例)

>>> import calendar
>>> from datetime import datetime, timedelta, timezone
>>> jst = timezone(timedelta(hours=+9), 'JST')
>>> datetime(2019,9,12,0,0,0,0).astimezone()
datetime.datetime(2019, 9, 12, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(0, 32400), 'JST'))
>>> utc = timezone(timedelta(hours=0), 'UTC')
>>> datetime(2019,9,12,0,0,0,0).astimezone(utc)
datetime.datetime(2019, 9, 11, 15, 0, tzinfo=datetime.timezone(datetime.timedelta(0), 'UTC'))
>>> calendar.timegm(datetime(2019,9,12,0,0,0,0).astimezone(jst).utctimetuple())
1568214000
>>> calendar.timegm(datetime(2019,9,12,0,0,0,0).astimezone(utc).utctimetuple())
1568214000

1568214000 、同じ値になります。

>>> import pytz
utc.localize(datetime.utcnow())
>>> datetime.timestamp(pytz.utc.localize(datetime.utcnow()))
1569115999.410595

datetime型 => 日付型

読みやすいように日付型に日時データを変換する場合です。

>>> from datetime import datetime, timedelta, timezone
>>> jst = timezone(timedelta(hours=+9), 'JST'
>>> datetime.now(tz=jst).strftime('%Y-%m-%d %H:%M:%S%z')
'2019-09-22 09:45:17+0900'
>>> datetime.now(tz=jst).__format__('%Y-%m-%d %H:%M:%S%z')
'2019-09-22 09:45:56+0900'
>>> datetime(2019,9,21,tzinfo=jst).isoformat(sep=' ', timespec='seconds')
'2019-09-21 00:00:00+09:00'
>>> datetime.now(tz=jst).isoformat(sep=' ', timespec='seconds')
'2019-09-22 09:47:53+09:00'

loggingモジュールで使う場合

from datetime import datetime, timedelta, timezone
import logging
import time
import pytz
jst = pytz.timezone('Asia/Tokyo')
def localTime(*args):
    utc_now = datetime.utcnow() 
    local_now = utc_now.replace(tzinfo=pytz.utc).astimezone(jst)
    return local_now.timetuple()

logging.basicConfig(format="%(asctime)s %(message)s",
                        datefmt="%Y-%m-%d %H:%M:%S")
logging.Formatter.converter = localTime
logger = logging.getLogger(__name__)
logger.error("localTime")

メモ

'''
[words]
UNIX time: エポック秒: 1970/1/1 00:00:00
naive: TZを扱わない: timezone = False
aware: TZを扱う    : timezone = True
'''

'''
[data type]
string: 日時の文字列(扱いが少ない)
e.g)
'13:07:29'
'2019-09-21'

unixtime: int(secs) or flaot(secs)
e.g)
1569057147.0

datetime: datetime型
e.g)
datetime(2019, 9, 21, 6, 6, 6, 6, tzinfo=jst)

struct_time(named tuple): strptime型、名前付きタプル
e.g)
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=21, tm_hour=8, tm_min=46, tm_sec=18, tm_wday=5, tm_yday=264, tm_isdst=0)
'''


#####################################
#          time module              #
#####################################

time.asctime([t])
'''
e.g)
>>> import time
>>> time.asctime()
'Sat Sep 21 17:44:07 2019'

   'Sun Jun 20 23:21:05 1993' といった書式の文字列に変換します。
   t が与えられていない場合には、localtime() が返す現在の時刻が使われます。
   asctime() はロケール情報を使いません。
'''

time.ctime([secs])
'''
e.g)
>>> time.ctime()
'Sat Sep 21 17:45:21 2019'

   エポックからの経過秒数で表現された時刻を、ローカルの時刻を表現する文字列に変換します。
   secs を指定しないか None を指定した場合、time() が返す値を現在の時刻として使用します。
   ctime(secs) は asctime(localtime(secs)) と等価です。ローカル情報は ctime() には使用されません。
'''

time.gmtime([secs])
'''
e.g)

>>> time.gmtime()
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=21, tm_hour=8, tm_min=46, tm_sec=18, tm_wday=5, tm_yday=264, tm_isdst=0)

   エポックからの経過時間で表現された時刻を、UTC で struct_time に変換します。
   このとき dst フラグは常にゼロとして扱われます。secs を指定しないか None を指定した場合、
   time() が返す値を現在の時刻として使用します。秒の端数は無視されます。
   struct_time オブジェクトについては前述の説明を参照してください。
   calendar.timegm() はこの関数と逆の変換を行います。
'''

time.localtime([secs])
'''
e.g)
>>> time.localtime()
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=21, tm_hour=17, tm_min=46, tm_sec=49, tm_wday=5, tm_yday=264, tm_isdst=0)

   gmtime() に似ていますが、ローカル時間に変換します。secs を指定しないか None を指定した場合、
   time() が返す値を現在の時刻として使用します。DST が適用されている場合は dst フラグには 1 が設定されます。
'''

time.mktime(t)
'''
e.g)
>>> now = datetime.datetime.now()
>>> print(now)
2019-09-21 18:12:27.703640
>>> print(now.timetuple())
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=21, tm_hour=18, tm_min=12, tm_sec=27, tm_wday=5, tm_yday=264, tm_isdst=-1)
>>> time.mktime(now.timetuple())
1569057147.0

   localtime() の逆を行う関数です。引数は struct_time か 9 個の要素すべての値を持つ完全なタプル
   (dst フラグも必要です; 時刻に DST が適用されるか不明の場合は -1 を使用してください) で、
   UTC ではなく ローカル 時間を指定します。戻り値は time() との互換性のために浮動小数点数になります。
   入力した値を正しい時刻として表現できない場合、例外 OverflowError または ValueError が送出されます 
   (どちらが送出されるかは、無効な値を受け取ったのが Python と下層の C ライブラリのどちらなのかによって決まります)。
   この関数で時刻を生成できる最も古い日付はプラットフォームに依存します。
'''


time.strftime(format[, t])
'''
e.g)
>>> time.strftime('%Y-%m-%d %H:%M:%S', (2019, 9, 21, 6, 6, 6, 6, 6, 6))
'2019-09-21 06:06:06'
>>> time.strftime('%Y-%m-%d %H:%M:%S', datetime.now().timetuple())
'2019-09-22 00:45:16'

   gmtime() や localtime() が返す時刻値タプルまたは struct_time を、format で指定した文字列形式に変換します。
   t が与えられていない場合、localtime() が返す値を現在の時刻として使用します。format は文字列でなくてはなりません。
   t のいずれかのフィールドが許容範囲外の数値であった場合、ValueError を送出します。
'''

time.strptime(string[, format])
'''
e.g)
>>> time.strptime('2019-09-21 06:06:06', '%Y-%m-%d %H:%M:%S')
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=21, tm_hour=6, tm_min=6, tm_sec=6, tm_wday=5, tm_yday=264, tm_isdst=-1)

   時刻を表現する文字列を書式に従って解釈します。返される値は gmtime() や localtime() が返すような struct_time です。
'''

time.time() :: float(t)
'''
e.g)
>>> time.time()
1569057980.8580966

エポック からの秒数を浮動小数点数で返します。 エポックの具体的な日付とうるう秒 (leap seconds) の扱いはプラットフォーム依存です。
   Windows とほとんどの Unix システムでは、エポックは (UTC で) 1970 年 1 月 1 日 0 時 0 分 0 秒で、
   うるう秒はエポック秒の時間の勘定には入りません。 これは一般に Unix 時間 と呼ばれています。
   与えられたプラットフォームでエポックが何なのかを知るには、 time.gmtime(0) の値を見てください。
   時刻は常に浮動小数点数で返されますが、すべてのシステムが 1 秒より高い精度で時刻を提供するとは限らないので注意してください。
   この関数が返す値は通常減少していくことはありませんが、この関数を 2 回呼び出し、
   その呼び出しの間にシステムクロックの時刻を巻き戻して設定した場合には、以前の呼び出しよりも低い値が返ることがあります。
'''

time.tzset()
'''
>>> os.environ['TZ'] = 'Asia/Tokyo'
>>> time.tzset()
>>> time.ctime()
'Sat Sep 21 18:32:34 2019'
>>> os.environ['TZ'] = 'UTC'
>>> time.tzset()
>>> time.ctime()
'Sat Sep 21 09:32:59 2019'

   Reset the time conversion rules used by the library routines. The environment variable TZ specifies how this is done.
   It will also set the variables tzname (from the TZ environment variable), timezone (non-DST seconds West of UTC),
   altzone (DST seconds west of UTC) and daylight (to 0 if this timezone does not have any daylight saving time rules,
    or to nonzero if there is a time, past, present or future when daylight saving time applies).

   注釈 多くの場合、環境変数 TZ を変更すると、 tzset() を呼ばない限り localtime() のような関数の出力に影響を及ぼすため、
   値が信頼できなくなってしまいます。

'''



#####################################
#          datetime module          #
#####################################

'''
Available Types: class
datetime.date - Attributes: year, month, and day
datetime.time - Attributes: hour, minute, second, microsecond, and tzinfo
datetime.date - Attributes: year, month, day, hour, minute, second, microsecond, and tzinfo
datetime.timedelta - A duration expressing the difference between two date, time, or datetime instances to microsecond resolution.
datetime.tzinfo - An abstract base class for time zone information objects. 
datetime.timezone - A class that implements the tzinfo abstract base class as a fixed offset from the UTC.

NOTE:
Objects of these types are immutable.
Objects of the date type are always naive.

An object of type time or datetime may be naive or aware.
A datetime object d is aware if d.tzinfo is not None and d.tzinfo.utcoffset(d) does not return None.
If d.tzinfo is None, or if d.tzinfo is not None but d.tzinfo.utcoffset(d) returns None, d is naive.
A time object t is aware if t.tzinfo is not None and t.tzinfo.utcoffset(None) does not return None. Otherwise, t is naive.


Structure:
Module         Subclass object:
datetime --+-- timedelta
           |
           +-- tzinfo ----- timezone
           |
           +-- time   --+
           |            +-- datetime
           +-- date   --+
'''

#### date object ####
'''
from datetime import datetime, date
'''
datetime.date(year, month, day)
'''
e.g)
>>> datetime.date(datetime(2019, 9, 21, 10, 9, 46, 358061))
datetime.date(2019, 9, 21)

datatype: datetime

All arguments are required. Arguments must be integers in the following ranges:
MINYEAR <= year <= MAXYEAR
1 <= month <= 12
1 <= day <= 指定された月と年における日数
'''

date.today()
'''
e.g)
>>> date.today()
datetime.date(2019, 9, 21)

   現在のローカルな日付を返します。date.fromtimestamp(time.time()) と等価です
'''

date.fromtimestamp(timestamp)
'''
e.g)
>>> now = time.time()
>>> date.fromtimestamp(now)
datetime.date(2019, 9, 21)

   time.time() で返されるような POSIX タイムスタンプに対応するローカルな日付を返します。
'''

date.timetuple()
'''
e.g)
>>> date.timetuple(date.today())
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=21, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=5, tm_yday=264, tm_isdst=-1)

   time.localtime() が返す形式の time.struct_time を返します。
   時間、分、および秒は 0 で、DST フラグは -1 になります。 d.timetuple() は次の値と同値です:
   time.struct_time((d.year, d.month, d.day, 0, 0, 0, d.weekday(), yday, -1)) ただし
   yday = d.toordinal() - date(d.year, 1, 1).toordinal() + 1 が 1月1日に 1 で始まる現在の年の日を表す。
'''

date.isoformat()
'''
e.g)
>>> date(2019, 9, 21).isoformat()
'2019-09-21'

   ISO 8601 形式、 'YYYY-MM-DD' の日付を表す文字列を返します。
'''

date.__str__()
'''
e.g)
>>> date(2019, 9, 21).__str__()
'2019-09-21'

   date オブジェクト d において、str(d) は d.isoformat() と等価です。
'''

date.ctime()
'''
e.g)
>>> date(2019, 9, 21).ctime()
'Sat Sep 21 00:00:00 2019'

   日付を表す文字列を、例えば date(2002, 12, 4).ctime() == 'Wed Dec 4 00:00:00 2002' のようにして返します。
   ネイティブの C 関数 ctime() (time.ctime() はこの関数を呼び出しますが、
   date.ctime() は呼び出しません) が C 標準に準拠しているプラットフォームでは、
   d.ctime() は time.ctime(time.mktime(d.timetuple())) と等価です
'''

date.strftime(format)
'''
e.g)
>>> date(2019, 9, 21).strftime('%Y-%m-%d')
'2019-09-21'

  明示的な書式文字列で制御された、日付を表現する文字列を返します。 時間、分、秒を表す書式化コードは値 0 になります。
   完全な書式化ディレクティブのリストについては strftime() と strptime() の振る舞い を参照してください。
'''

date.__format__(format)
'''
e.g)
>>> date(2019, 9, 21).__format__('%Y-%m-%d')
'2019-09-21'

   date.strftime`と等価です。これにより、 :meth:`str.format() の使用時に date の書式文字列を指定できます。
   書式化コードの完全なリストについては strftime() と strptime() の振る舞い を参照してください。
'''

#### datetime object ####

'''
   datetime オブジェクトは date オブジェクトおよび time オブジェクトの全ての情報が入っている単一のオブジェクトです。
   date オブジェクトと同様に、 datetime は現在のグレゴリオ暦が両方向に延長されているものと仮定します;
   また、 time オブジェクトと同様に, datetime は毎日が厳密に 3600*24 秒であると仮定します。
'''

datetime.datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0)
'''
e.g)
>>> jst = timezone('Asia/Tokyo')
>>> datetime(2019, 9, 21, 6, 6, 6, 6, tzinfo=jst)

   The year, month and day arguments are required. tzinfo may be None, or an instance of a tzinfo subclass.
   The remaining arguments must be integers in the following ranges:
'''

datetime.today()
'''
e.g)
>>> datetime.today()
datetime.datetime(2019, 9, 21, 10, 56, 41, 267091)

   現在のローカルな datetime を返します。 tzinfo は None です。 この関数は datetime.fromtimestamp(time.time()) と等価です。
   now(), fromtimestamp() も参照してください
'''

datetime.now(tz=None)
'''
e.g)
>>> datetime.now(tz=None)
datetime.datetime(2019, 9, 21, 10, 57, 16, 74633)
>>> jst = timezone(timedelta(hours=+9), 'JST')
>>> datetime.now(tz=jst)
datetime.datetime(2019, 9, 21, 19, 58, 4, 603812, tzinfo=datetime.timezone(datetime.timedelta(seconds=32400), 'JST')

   現在のローカルな日付および時刻を返します。オプションの引数 tz が None であるか指定されていない場合、
   このメソッドは today() と同様ですが、可能ならば time.time() タイムスタンプを通じて得ることができる、
   より高い精度で時刻を提供します (例えば、プラットフォームが C 関数 gettimeofday() をサポートする場合には可能なことがあります)。
   tz が None でない場合、 tz は tzinfo のサブクラスのインスタンスでなければならず、
   現在の日付および時刻は tz のタイムゾーンに変換されます。
   この場合、結果は tz.fromutc(datetime.utcnow().replace(tzinfo=tz)) と等価になります。 today(), utcnow() も参照してください。
'''

datetime.utcnow()
'''
e.g)
>>> datetime.utcnow()
datetime.datetime(2019, 9, 21, 11, 1, 26, 644089)
>>> from pytz import utc
>>> import pytz
>>> datetime.now(pytz.utc)
datetime.datetime(2019, 9, 21, 11, 8, 11, 517935, tzinfo=<UTC>)

   tzinfo が None である現在の UTC の日付および時刻を返します。 これは now() と似ていますが、
   naive な datetime オブジェクトとして現在の UTC 日付および時刻を返します。
   aware な現在の UTC datetime は datetime.now(timezone.utc) を呼び出すことで取得できます。 now() も参照してください。
'''

datetime.fromtimestamp(timestamp, tz=None)
'''
e.g)
>>> time.time()
1569064464.2774587
>>> datetime.fromtimestamp(time.time(), tz=None)
datetime.datetime(2019, 9, 21, 11, 14, 50, 455411)
>>> datetime.fromtimestamp(time.time(), tz=jst)
datetime.datetime(2019, 9, 21, 20, 15, 9, 629853, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)

   time.time() が返すような、 POSIX タイムスタンプに対応するローカルな日付と時刻を返します。
   オプションの引数 tz が None であるか、指定されていない場合、
   タイムスタンプはプラットフォームのローカルな日付および時刻に変換され、返される datetime オブジェクトは naive なものになります。
   tz が None でない場合、 tz は tzinfo のサブクラスのインスタンスでなければならず、
   現在の日付および時刻は tz のタイムゾーンに変換されます。
   この場合、結果は tz.fromutc(datetime.utcfromtimestamp(timestamp).replace(tzinfo=tz)) と等価になります。
'''


datetime.utcfromtimestamp(timestamp)
'''
e.g)
>>> datetime.utcfromtimestamp(time.time())
datetime.datetime(2019, 9, 21, 11, 15, 49, 533910)

   与えられた POSIX タイムスタンプに対応する UTC の datetime で、 tzinfo が None に設定されたものを返します。
   タイムスタンプがプラットフォームの C 関数 gmtime() でサポートされている範囲を超えている場合には OverflowError を、
   gmtime() が失敗した場合には OSError を送出します。 サポートされている範囲は 1970 年から 2038 年に制限されていることが多いです。
   aware な datetime オブジェクトを得るには fromtimestamp() を呼んでください:
   datetime.fromtimestamp(timestamp, timezone.utc)
   POSIX 互換プラットフォームでは、これは以下の表現と等価です:
   datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=timestamp)
'''

Python3.7: datetime.fromisoformat(date_string)
'''
e.g)
>>> datetime.fromisoformat('2019-09-21')
datetime.datetime(2019, 9, 21, 0, 0)
>>> date.fromisoformat('2019-09-21')
datetime.date(2019, 9, 21)

   Return a datetime corresponding to a date_string in one of the formats emitted by date.isoformat() and datetime.isoformat().
   Specifically, this function supports strings in the format(s) YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]],
   where * can match any single character.
   ご用心 This does not support parsing arbitrary ISO 8601 strings -
   it is only intended as the inverse operation of datetime.isoformat().
   A more full-featured ISO 8601 parser, dateutil.parser.isoparse is available in the third-party package dateutil.
'''

datetime.strptime(date_string, format)
'''
e.g)
>>> from datetime import  time, datetime
>>> datetime.strptime('2019-09-21 06:06:06', '%Y-%m-%d %H:%M:%S')
datetime.datetime(2019, 9, 21, 6, 6, 6)

date_string に対応した datetime を返します。 format にしたがって構文解析されます。
   これは、 datetime(*(time.strptime(date_string, format)[0:6])) と等価です。
'''

#### instance method ####

datetime.date()
'''
e.g)
>>> from timedate import date
>>> date(2019, 09, 21)
  File "<stdin>", line 1
    date(2019, 09, 21)
                ^
SyntaxError: invalid token
>>> date(2019, 9, 21)
datetime.date(2019, 9, 21)


同じ年、月、日の date オブジェクトを返します。
'''

datetime.time()
'''
e.g)
>>> from datetime import time
>>> time(11, 7, 9)
datetime.time(11, 7, 9)

同じhour、minute、second、microsecond 及び foldを持つ time オブジェクトを返します。 tzinfo は None です。
   timetz() も参照してください。
'''


datetime.astimezone(tz=None)
'''
e.g)
>>> from datetime import datetime, timedelta, timezone
>>> datetime(2019,9,12,0,0,0,0).astimezone()
datetime.datetime(2019, 9, 12, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(0), 'UTC'))
>>> jst = timezone(timedelta(hours=+9), 'JST')
>>> datetime(2019,9,12,0,0,0,0).astimezone(jst)
datetime.datetime(2019, 9, 12, 9, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=32400), 'JST')


   tz を新たに tzinfo 属性 として持つ datetime オブジェクトを返します。 日付および時刻データを調整して、
   返り値が self と同じ UTC 時刻を持ち、 tz におけるローカルな時刻を表すようにします。
   もし与えられた場合、 tz は tzinfo のサブクラスのインスタンスでなければならず、
   インスタンスの utcoffset() および dst() メソッドは None を返してはなりません。
   もし self が naive ならば、おそらくシステムのタイムゾーンで時間を表現します。
   引数無し (もしくは tz=None``の形 ) で呼び出された場合、変更先のタイムゾーンはシステムのローカルなタイムゾーンだと想定されます。
   変換後の datetime インスタンスの ``.tzinfo 属性には、
   OS から取得したゾーン名とオフセットを持つ timezone インスタンスが設定されます。
   self.tzinfo が tz の場合、 self.astimezone(tz) は self に等しくなります。
   つまり、date および time に対する調整は行われません。そうでない場合、結果はタイムゾーン tz におけるローカル時刻で、
   self と同じ UTC 時刻を表すようになります。これは、astz = dt.astimezone(tz) とした後、
   astz - astz.utcoffset() は通常 dt - dt.utcoffset() と同じ date および time を持つことを示します。
   単にタイムゾーンオブジェクト tz を datetime オブジェクト dt に追加したいだけで、
   日付や時刻データへの調整を行わないのなら、dt.replace(tzinfo=tz) を使ってください。
   単に aware な datetime オブジェクト dt からタイムゾーンオブジェクトを除去したいだけで、
   日付や時刻データの変換を行わないのなら、dt.replace(tzinfo=None) を使ってください
'''

datetime.timetuple()
'''
e.g)
>>> import time
>>> now = time.time()
>>> datetime.fromtimestamp(now)
datetime.datetime(2019, 9, 21, 12, 47, 28, 217215)
>>> datetime.fromtimestamp(now).timetuple()
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=21, tm_hour=12, tm_min=47, tm_sec=28, tm_wday=5, tm_yday=264, tm_isdst=-1)

   time.localtime() が返す形式の time.struct_time を返します。
   d.timetuple() は time.struct_time((d.year, d.month, d.day, d.hour, d.minute, d.second, d.weekday(), yday, dst)) と等価です。
'''

datetime.utctimetuple()
'''
e.g)
>>> datetime.fromtimestamp(now).utctimetuple()
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=21, tm_hour=12, tm_min=47, tm_sec=28, tm_wday=5, tm_yday=264, tm_isdst=0)

   datetime インスタンス d が naive の場合、このメソッドは d.timetuple() と同じであり、
   d.dst() の返す内容にかかわらず tm_isdst が 0 に強制される点だけが異なります。
   DST が UTC 時刻に影響を及ぼすことは決してありません。
   d が aware だった場合、 d は d.utcoffset() を引いて UTC 時刻に正規化され、
   その時刻が time.struct_time として返されます。 tm_isdst は 0 に強制されます。
   d.year が MINYEAR もしくは MAXYEAR であり、 UTC 時刻への調整により適切な年の範囲を越えた場合、
   OverflowError が送出される可能性があることに注意してください。
'''

datetime.timestamp()
'''
e.g)
>>> datetime.timestamp(datetime.now())
1569070325.080109

   datetime インスタンスに対応する POSIX タイムスタンプを返します。 返り値は time.time() で返される値に近い float です。
   このメソッドでは naive な datetime インスタンスはローカル時刻とし、プラットフォームの C 関数 mktime() に頼って変換を行います。
   datetime は多くのプラットフォームの mktime() より広い範囲の値をサポートしているので、遥か過去の時刻や遥か未来の時刻に対し、
   このメソッドは OverflowError を送出するかもしれません
   注釈 UTC 時刻を表す naive な datetime インスタンスから直接 POSIX タイムスタンプを取得するメソッドはありません。
   アプリケーションがその変換を使っており、システムのタイムゾーンが UTC に設定されていなかった場合、
   tzinfo=timezone.utc を引数に与えることで POSIX タイムスタンプを取得できます:
   timestamp = dt.replace(tzinfo=timezone.utc).timestamp()
   もしくは直接タイムスタンプを計算することもできます:
   timestamp = (dt - datetime(1970, 1, 1)) / timedelta(seconds=1)
'''

datetime.isoformat(sep='T', timespec='auto')
'''
e.g)
>>> datetime.now()
datetime.datetime(2019, 9, 21, 12, 53, 41, 160655)
>>> datetime.now().isoformat(sep='T', timespec='auto')
'2019-09-21T12:53:08.617515

   Return a string representing the date and time in ISO 8601 format,
   YYYY-MM-DDTHH:MM:SS.ffffff or, if microsecond is 0, YYYY-MM-DDTHH:MM:SS
'''

datetime.ctime()
'''
e.g)
>>> datetime(2019,9,21,22,0,0).ctime()
'Sat Sep 21 22:00:00 2019

   日付を表す文字列を、例えば datetime(2002, 12, 4, 20, 30, 40).ctime() == 'Wed Dec  4 20:30:40 2002' のようにして返します。
   ネイティブの C 関数 ctime() (time.ctime() はこの関数を呼び出しますが、
   datetime.ctime() は呼び出しません) が C 標準に準拠しているプラットフォームでは、
   d.ctime() は time.ctime(time.mktime(d.timetuple())) と等価です
'''

datetime.strftime(format)
'''
e.g)
>>> datetime(2019, 9, 21, 0, 0, 0).strftime('%Y-%m-%d %H:%M:%S')
'2019-09-21 00:00:00'

  明示的な書式文字列で制御された、日付および時刻を表現する文字列を返します。
   完全な書式化ディレクティブのリストについては strftime() と strptime() の振る舞い を参照してください。
'''

datetime.__format__(format)
'''
e.g)
>>> datetime(2019, 9, 21, 0, 0, 0).__format__('%Y-%m-%d %H:%M:%S')
'2019-09-21 00:00:00

   date.strftime`と等価です。これにより、 :meth:`str.format() の使用時に datetime の書式文字列を指定できます。
   書式化コードの完全なリストについては strftime() と strptime() の振る舞い を参照してください。
'''


#### time object ####

datetime.time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0)
'''
e.g)
>>> datetime.now()
datetime.datetime(2019, 9, 21, 13, 7, 29, 562610)
>>> datetime.time(datetime.now())
datetime.time(13, 7, 43, 218598)

   All arguments are optional. tzinfo may be None, or an instance of a tzinfo subclass.
   The remaining arguments must be integers in the following ranges:

    0 <= hour < 24,
    0 <= minute < 60,
    0 <= second < 60,
    0 <= microsecond < 1000000,
    fold in [0, 1].
'''

Pytohn 3.7: time.fromisoformat(time_string)
'''
e.g)
>>> from datetime import time
>>> time.fromisoformat('13:7:29')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Invalid isoformat string: '13:7:29'
>>> time.fromisoformat('13:07:29')
datetime.time(13, 7, 29)

   Return a time corresponding to a time_string in one of the formats emitted by time.isoformat().
   Specifically, this function supports strings in the format(s) HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]].
   ご用心 This does not support parsing arbitrary ISO 8601 strings -
   it is only intended as the inverse operation of time.isoformat().
'''

time.replace(hour=self.hour, minute=self.minute, second=self.second, microsecond=self.microsecond, tzinfo=self.tzinfo, * fold=0)
'''
e.g)
>>> time(13, 7, 29).replace(1,1,1)
datetime.time(1, 1, 1)

   キーワード引数で指定したメンバの値を除き、同じ値をもつ time オブジェクトを返します。
   データに対する変換を行わずに aware な time オブジェクトから naive な time オブジェクトを生成するために、
   tzinfo=None を指定することもできます。
'''

time.isoformat(timespec='auto')
'''
e.g)
>>> time(13, 7, 29).isoformat(timespec='auto')
'13:07:29'

   Return a string representing the time in ISO 8601 format, HH:MM:SS.ffffff or,
   if microsecond is 0, HH:MM:SS If utcoffset() does not return None, a string is appended,
   giving the UTC offset: HH:MM:SS.ffffff+HH:MM[:SS[.ffffff]] or, if self.microsecond is 0, HH:MM:SS+HH:MM[:SS[.ffffff]].
'''

#### tzinfo object ####
'''
'''

#### timezone object ####

datetime.timezone(offset, name=None)
'''
e.g)
>>> from datetime import timezone, timedelta, datetime
>>> jst = timezone(timedelta(hours=+9), 'JST')
>>> datetime.timestamp(datetime.now())
1569072574.300513
>>> datetime.fromtimestamp(datetime.timestamp(datetime.now()), jst)
datetime.datetime(2019, 9, 21, 22, 33, 0, 853514, tzinfo=datetime.timezone(datetime.timedelta(seconds=32400), 'JST'))

   ローカル時刻と UTC の差分を表す timedelta オブジェクトを offset 引数に指定しなくてはいけません。
   これは -timedelta(hours=24) から timedelta(hours=24) までの両端を含まない範囲に収まっていなくてはなりません。
   そうでない場合 ValueError が送出されます。
'''

#####################################
#          calendar module          #
#####################################

calendar.timegm(named tuple)
'''
e.g)
>>> import calendar
>>> datetime.utcnow().utctimetuple()
time.struct_time(tm_year=2019, tm_mon=9, tm_mday=21, tm_hour=13, tm_min=20, tm_sec=19, tm_wday=5, tm_yday=264, tm_isdst=0)
>>> calendar.timegm(datetime.utcnow().utctimetuple())
1569071993

   カレンダーと直接は関係無いが、 time モジュールの gmtime() 関数が返す形式の時刻を表すタプルを引数に取り、
   1970 を基点とするエポック時刻で POSIX エンコーディングであると仮定して、対応する Unix タイムスタンプの値を返します。
   実際には、 time.gmtime() と timegm() はお互いの逆関数です。
'''


#####################################
#           pytz module             #
#####################################

'''
e.g)
>>> from pytz import timezone
>>> import pytz
>>> utc = pytz.utc
>>> utc.zone
'UTC'
>>> eastern = timezone('US/Eastern')
>>> eastern.zone
'US/Eastern'
>>> amsterdam = timezone('Europe/Amsterdam')
>>> fmt = '%Y-%m-%d %H:%M:%S %Z' 
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0))
>>> print(loc_dt.strftime(fmt))
2002-10-27 06:00:00 EST
>>> ams_dt = loc_dt.astimezone(amsterdam)
>>> ams_dt.strftime(fmt)
'2002-10-27 12:00:00 CET'

Unfortunately using the tzinfo argument of the standard datetime constructors ‘’does not work’’ with pytz for many timezones.
'''

systemdのユーザモードを使う

Botプログラムが通信環境が不安定になると落ちるのでcronとかshellのループを使ってプログラムを監視していたのですが、VPSサーバのロード負荷が高い場合、複数Botが立ち上がったりなどあまり連携がよくありませんでした。そんななかsystemdでユーザモードが使えることがわかったので使ってみることにしました。とくにsystemdで有効なのはデーモンの再起動機能です。ユーザモードでBotを立ち上げると、何らかの原因でプログラムが終了してもすぐに再起動してくれます。

systemdのsystemモードとuserモードの関係はArchlinux WIKIから引用ですが、わかりやすい説明があります。

systemd –user は systemd –system プロセスとは別のプロセスを起動します。ユーザーユニットからシステムユニットに依存したり参照することはできません。

つまり、systemモードとuserモードは排他的関係でお互いに独立したシステムというとになります。またuserモードで使えるディレクトリにも制約があります。ArchLinux WIKIで見てみるとつぎのようになっています。

  • /usr/lib/systemd/user/: インストールしたパッケージに含まれているサービス
  • ~/.local/share/systemd/user/: ホームディレクトリにインストールしたパッケージのユニット
  • /etc/systemd/user/: システムの管理者が配置する、全てのユーザーが使えるユーザーサービス
  • ~/.config/systemd/user/: ユーザーが配置する、そのユーザーのサービス

このことを念頭において作業に取り掛かります。まずテストにオレオレサービスを作ってみます。

~/.config/systemd/user/oreoreservice.service

[Unit]
Description=test unit

[Service]
ExecStart=/home/user1/bin/oreoreservice
ExecStopPost=/home/user/bin/systemd-email %n

Restart=always
StandardOutput=journal
StandardError=journal
Type=simple

[Install]
WantedBy=default.target

前回、設定したコマンドラインメールからEmailレポートします。

~/bin/systemd-email

#!/bin/bash

SUBJECT="Error report: $1"
RECIPIENT='krishna@mydomain.tld'

/usr/bin/mutt -s "$SUBJECT" "$RECIPIENT" <<ERRMAIL
$(systemctl --user status --full $1)
ERRMAIL

起動します。

systemctl --user enable oreoreservice.service
systemctl --user daemon-reload
systemctl --user start oreoreservice.service

Emailが送られるかどうかチェックしてみます。

systemctl –user kill oreoreservice.service -s SIGKILL

最後にsystemdのユーザインスタンスの自動起動をONにします。これでブート時に自動実行できるようになります。

loginctl enable-linger username

以上です。

追記1:

systemdの資料を読んでいたらもっと良い方法を思いつきました。

oreoreservice.service

[Unit]
Description=test unit
OnFailure=status-report@%n.service

[Service]
ExecStart=/home/user/bin/oreoreservice
Restart=always
StandardOutput=journal
StandardError=journal
Type=simple

[Install]
WantedBy=default.target

status-report@.service

[Unit]
Description=status email for %i to user

[Service]
Type=oneshot
ExecStart=/home/shiva/bin/systemd-email %i

[Install]
WantedBy=default.target

サービスを登録します。

systemctl –user enable status-report@oreoreservice.service.service

テスト:

まず起動します。

systemctl --user start oreoreservice.service
systemctl --user status oreoreservice.service

ステータスで起動していることを確認します。

chmod 644 /home/user/bin/oreoreservice
systemctl --user kill oreoreservice.service -s SIGKILL

つぎにパーミッションを変更して実行できないようにします。killシグナルを送り再起動させます。当然ながらプログラムが実行できないのでエラーになりUnitのOnFailureオプションが発動されます。

追記2:

journalctlでログを取る場合、次のエラーが出ます。

journalctl --user -u bitmex_database.service
Hint: You are currently not seeing messages from the system.
      Users in the 'systemd-journal' group can see all messages. Pass -q to
      turn off this notice.

このエラーを修正するには次のようにします。

/etc/systemd/journald.conf

[Journal]
Storage=persistent
...

コンテナを再起動します。

journalctl --user -u bitmex_database.service
-- Logs begin at Sun 2019-09-15 00:26:22 UTC, end at Sun 2019-09-15 00:26:23 UTC. --
Sep 15 00:26:23 srv0102mex systemd[322]: Started Bitmex database service.
Sep 15 00:26:23 srv0102mex systemd[336]: Failed to attach 336 to compat systemd cgroup /user.slice/user-1000.slice/user@1000.service/bitmex_database

このように正常にログを出力できるようになります。

note:’Failed to attach 336 to compat systemd cgroup’はlxc側の問題なので現在のところプログラムの実行には影響ありません。

参考:

Muttでコマンドラインからメールを送信

systemdにはユーザモードがありBotなどをユーザ権限でデーモン化することができます。さらにsystemdを使うことで再起動機能やログの管理等のメリットが享受できます。またBotが原因不明で止まった場合メールで知らせる機能を追加しようと考えてコマンドラインから使えるメールソフトを探してみました。そのなかでMuttがもっとも機能的に受け入れられたので採用することにしました。ところが設定が難しくなかなかメールサーバにログインできず、いろいろ検索した結果できるようになりました。そのときのテンプレート用に使えるファイルと載せておきます。

Muttのユーザ用の設定ファイルは$HOME/.muttrcです。

set realname = 'botmail123'
set from = botmail123@mydomain.tld
set use_from = yes

set smtp_url = "smtp://botmail123@mydomain.tld@mx.mydomain.tld:587/"
set smtp_pass = "xxxxxxxxxxxxxxx"
set imap_user = "botmail123@mydomain.tld"
set imap_pass = "xxxxxxxxxxxxxxx"

set folder = "imaps://mx.mydomain.tld:993"
set spoolfile = "+INBOX"

set header_cache=~/.mutt/cache/headers
set message_cachedir=~/.mutt/cache/bodies
set certificate_file=~/.mutt/certificates

set mail_check = 30
set move = no
set imap_keepalive = 900
set sort = threads
set editor = "nano"
set send_charset="us-ascii:utf-8"

# GnuPG bootstrap
# source ~/.mutt/gpg.rc

 

操作方法:Archlinux WIKIより

一般の方法

mutt -s "Subject" somejoeorjane@someserver.com < mail.txt

もちろんパイプでもできます

echo 'hello from mydomain' | mutt -s "Subject" somejoeorjane@someserver.com

長文の場合

mutt -s "Subject" somejoeorjane@someserver.com <<'EOF'
> 拝啓
> 初冬のみぎり 皆様におかれましては益々ご清祥の事とお慶び申し上げます。
> この度は お心のこもった品物を頂戴いたしまして誠にありがとうございます。
> これから寒さの本番ですので 皆様どうぞご自愛くださいませ。
> まずは取り急ぎ 書中をもちまして御礼を申し上げます。
> 
> 敬具
> EOF

ファイルを添付する場合

mutt -s "Subject" somejoeorjane@someserver.com -a somefile < mail.txt

 

Bitmexのチャートをデータベースにインポートする

以前、投稿したBinanceでデータベース作成と同じ要領ですがBitmexの場合はいくつか制約がありそれをクリアしなければなりません。

  • 1分足、5分足、1時間足、日足以外のチャートはほかのチャートを利用して作成する
  • 一回につき10080件以上のデータを取ることができない
  • 150回/5分の制限がある

この3つの制約をクリアしたプログラムを作成しました。Pandasのリサンプリングで不完全なOHLCVを作らないため最大公約数として日足を基準にしています。

get_bitmex_data.py

#!/usr/bin/env python3
import time, calendar, pytz, requests, os, csv, json, time
from datetime import datetime, timedelta, date
import pandas as pd

user_config = {
        'date_from'       : (2019,1,1),
        'date_to'         : ('now'),
        'selected_list'   : [
                             '1m',
                             '3m',
                             '5m',
                             '15m',
                             '30m',
                             '1h',
                             '2h',
                             '4h',
                             '1d',
                            ],
}

# bitmex attributes
mex_params = {
        'max_counter'     : 10000,       # max 10080
        'api_rate_limit'  : 25,          # max 30/min, 150/5min
        'pair_symbol'     : 'XBTUSD',
        #'boolean_reverse' : False,
        #'boolean_partial' : False,
        #'tstype'          : 'UTMS',
        'bitmex_url_req'  : 'https://www.bitmex.com/api/udf/history?',
}

timeframe_list = {
        '1m'  : {'resample': False, 'base': '1m', 'tframe' : 1},
        '3m'  : {'resample': True,  'base': '1m', 'tframe' : 3},
        '5m'  : {'resample': False, 'base': '5m', 'tframe' : 5},
        '15m' : {'resample': True,  'base': '5m', 'tframe' : 15},
        '30m' : {'resample': True,  'base': '5m', 'tframe' : 30},
        '1h'  : {'resample': False, 'base': '1h', 'tframe' : 60},
        '2h'  : {'resample': True,  'base': '1h', 'tframe' : 120},
        '4h'  : {'resample': True,  'base': '1h', 'tframe' : 240},
        '1d'  : {'resample': False, 'base': '1d', 'tframe' : 1440},
}

def main():
    bitmex = {}
    combo_df = {}
    #target = '1h'
    target = user_config['selected_list']
    utime_from, utime_to = set_time()

    for n in target:
        if timeframe_list[n]['resample'] == True:
             base = timeframe_list[n]['base']
             if base in bitmex:
                 combo_df.update({n : df_resampling(combo_df[base], n)})
             else:
                 bitmex[base] = BitmexGetData(utime_from, utime_to, base)
                 combo_df.update({base : bitmex[base].get_data()})
                 combo_df.update({n : df_resampling(combo_df[base], n)})
        elif timeframe_list[n]['resample'] == False:
            if n not in bitmex:
                bitmex[n] = BitmexGetData(utime_from, utime_to, n)
                combo_df.update({n : bitmex[n].get_data()})

    # output to csv
    for key in combo_df:
        df = combo_df[key]
        filename = 'chart_' + key + '.csv'
        df.to_csv(filename, index=False)
        # test
        #print(df)

def set_time():
    date_from, date_to = user_config['date_from'], user_config['date_to']
    one_day = 60 * 60 * 24
    if type(date_to) == str and date_to == 'now':
        #utc_now = datetime.now(pytz.utc)
        utime_to = calendar.timegm(datetime.utcnow().utctimetuple())
        utime_to = utime_to // one_day * one_day
    elif type(date_to) == tuple and len(date_to) == 3:
        utime_to = calendar.timegm(date(date_to[0], date_to[1], date_to[2]).timetuple())
        utime_to = utime_to // one_day * one_day
    else:
        print('ERROR set_time(): invalid data')
        exit(1)

    utime_from = calendar.timegm(date(date_from[0], date_from[1], date_from[2]).timetuple())

    if utime_to < utime_from:
        print('ERROR set_time(): invalid data')
        exit(1)

    return int(utime_from), int(utime_to)

class BitmexGetData:
    def __init__(self, utime_from, utime_to, target):
        self.utime_from = utime_from
        self.utime_to   = utime_to
        self.target     = target

    def get_ohlcv(self):
        timeframe = timeframe_list[self.target]
        limit = mex_params['api_rate_limit']
        params = mex_params

        for start in range(self.utime_from, self.utime_to, timeframe['tframe'] * 60 * mex_params['max_counter']):
            end = start + timeframe['tframe'] * 60 * mex_params['max_counter']

            if end > self.utime_to:
                end = self.utime_to

            params.update({'from' : start, 'to' : end, 'timeframe' : timeframe['tframe']})
            url = '{bitmex_url_req}symbol={pair_symbol}&resolution={timeframe}&from={from}&to={to}'.format(**params)
            res = requests.get(url)
            data = res.json()

            yield data['t'], data['o'], data['h'], data['l'], data['c'], data['v']
            limit += -1

            if limit == 0:
                print('Maximum connection limit reached. Wait 60 seconds...')
                time.sleep(60)
                limit = mex_params['api_rate_limit']

    def get_data(self):
        mex_timestamp, mex_open, mex_high, mex_low, mex_close, mex_volume = [], [], [], [], [], []

        for _timestamp, _open, _high, _low, _close, _volume in BitmexGetData.get_ohlcv(self):
            mex_timestamp.extend(_timestamp)
            mex_open.extend(_open)
            mex_high.extend(_high)
            mex_low.extend(_low)
            mex_close.extend(_close)
            mex_volume.extend(_volume)

        mex_datetime = pd.to_datetime(mex_timestamp, unit='s')
        _index = pd.Index(mex_datetime)
        df = pd.DataFrame({'timestamp' : mex_timestamp, 'open' : mex_open,
                           'high' : mex_high, 'low' : mex_low, 'close' : mex_close,
                           'volume' : mex_volume}, index=_index).sort_values('timestamp')
        # Remove first row
        df = df.drop(df.index[0])
        return df

def df_resampling(df, timeframe):
    tframe = timeframe_list[timeframe]['tframe']

    if tframe < 60:
        new_timeframe = str(tframe) + 'T'
    elif tframe >= 60 and tframe < 1440:
        new_timeframe = str(tframe / 60) + 'H'
    elif tframe >= 1440:
        new_timeframe = str(tframe / 1440) + 'D'

    def_ohlcv = {'timestamp' : 'first', 'open' : 'first', 'high' : 'max', 'low' : 'min',
                 'close' : 'last', 'volume' : 'sum',}
    df = df.resample(new_timeframe).agg(def_ohlcv)

    return df

if __name__ == "__main__":
    main()

10分〜15分程度で各時間足のcsvファイルを書き出します。これをmysqlimportでデータベースにインポートします。

import_csv.sh

#!/bin/sh

csvfiles='chart_15m.csv
chart_1d.csv
chart_1h.csv
chart_1m.csv
chart_2h.csv
chart_30m.csv
chart_3m.csv
chart_4h.csv
chart_5m.csv'

for n in $csvfiles; do
    mysqlimport --ignore-lines=1 \
                --fields-terminated-by=, \
                --local -u guest -p \
                bitmex \
                $n
done

このスクリプトを作成にあたり、次のサイトを参考にしました。大変感謝しています。

追記:

numpyとPythonのListとどちらが速いのか分かりませんがつぎのようにnumpyに置き換かえてみます。

...

import numpy as np

...

            yield ([np.array(data[x]) for x in ['t', 'o', 'h', 'l', 'c', 'v']])            

...

    def get_data(self):
        mex_timestamp, mex_open, mex_high, mex_low, mex_close, mex_volume =  [np.array([]) for x in range(6)]

        for _timestamp, _open, _high, _low, _close, _volume in BitmexGetData.get_ohlcv(self):
            mex_timestamp = np.append(mex_timestamp, _timestamp)
            mex_open      = np.append(mex_open, _open)
            mex_high      = np.append(mex_high, _high)
            mex_low       = np.append(mex_low, _low)
            mex_close     = np.append(mex_close, _close)
            mex_volume    = np.append(mex_volume, _volume)

調べてみると次のようにありました。

NumPyのndarrayは多次元配列を扱うことを目的としたクラスで、事前にメモリ確保しています。np.append関数を使用すると、元のshapeが破壊されてしまうため、要素のコピーが発生して遅くなることがあります。一方で、Pythonのlistは可変長の要素を保持するベクタ型です。少し大きめの要素を事前に確保するので、毎回のコピーは発生しません。

https://deepage.net/features/numpy-append.html

numpyは計算は速いが可変長のリスト処理は遅いということです。

 

 

Proxy ARPの設定

今使っているNAS(ReadyNAS 102)はカスタムカーネルを使っているためFirewallが使えません。カスタムFirmwareを検索してみたところ、だいぶ前に開発が止まっていました。カスタムカーネルを入れ替えればいいのですが、そもそもリスクが大きいことと正規ファームウエアによるチェックでダッシュボードにアクセスできないという問題があるらしいのです。そこでブリッジにFirewallいれて同等の機能をもたせようと思案しても2つ以上ポートのある機材は手元にありません。そこでProxy ARPなら手持ちに機材で対応できるので設定することにしました。ルータはBuffalo WZR-HP-AG300Hです。このルータはLAN用のアダプタとWAN用のアダプタの2つのイーサネットデバイスを持っています。ルータの中には1つのイーサアタプターをVLANでWANとLANを分けている機種もあります。ふだん中間ルータとして使う分にはWAN用のアダプタをブリッジしてLAN用ポート増やす使い方してますが、今回、このWANポートのProxy ARPのブリッジ化をしてみます。

[Layout]
                        OpenWrt Router:
 NAS:                  +----------------+----------------+      PC:
 192.168.1.100/24 ==== | 192.168.1.1/24 | 192.168.1.2/24 | ==== 192.168.1.11/24
                       | ZONE:lan2      | ZONE:lan1      |
                       | IF  :eth1      | IF  :br-lan    |
                       +----------------+----------------+
                        192.168.1.100 dev eth1 proto static scope link
                        net.ipv4.conf.eth1.proxy_arp=1
                        net.ipv4.conf.br-lan.proxy_arp=1

DebianのWIKIにすべて書いてあるのでそれを参考にセットアップします。

手動で行うには次のようにします。

echo 1 > /proc/sys/net/ipv4/conf/br-lan/proxy_arp
echo 1 > /proc/sys/net/ipv4/conf/eth1/proxy_arp
echo 1 > /proc/sys/net/ipv4/ip_forward
ip route add 192.168.1.100/32 dev eth1
ip addr add 192.168.1.2/32 dev eth1
ip addr add 192.168.1.1/32 dev br-lan

ip_forwardは大抵のルータで1(enable)になっているはずなので設定の必要はありませんが明示的に設定しておきます。

DebianのWIKIにもありますがひとつだけ問題があります。DHCP Relayする必要があります。OpenWrtにはいくつかパッケージがありますが、それは使わずデフォルトで入っているdnsmasqにDHCP Relay機能があるのでこれを使います。

/etc/dnsmasq.conf

dhcp-option=option:dns-server,192.168.1.254
dhcp-option=3,192.168.1.254
dhcp-relay=192.168.1.2,192.168.1.254,br-lan

Proxy ARPのブリッジの確認ができたらダッシュボードでOpenWrtの設定をします。

Network > Static Routes

/etc/sysctl.d/99-proxy-arp.conf

net.ipv4.conf.eth1.proxy_arp=1
net.ipv4.conf.br-lan.proxy_arp=1

リブートして設定を反映させます。

Firewallは悩みましたがとりあえずフォワードで行けそうです。もっといい方法がありそうですが今のところ思いつきません。

サービスポートを調べて追加

Debian WIKIをみるとmDNSもリレーが必要のようでファイラではNASが見えません。アドレスを打ち込めば使えますが少々不便です。解決方法は調査中です。また速度が2/3程度になります。経路が複雑になった上にiptablesでフィルタをかけているのでどうしても遅くなります。

以上です。

pure-ftpdの設定&rsyncの設定

新しいVPSの設定はほぼ完了したのでバックアップ周りの設定をしました。WrodPressのデータなどをVPN経由のFTPで旧VPSへバックアップとるためにpure-ftpdの設定をしました。

問題はデーモンモードだとdbファイルを読めませんでした。原因が分からず調べまくってるうちにできてたみたいで今ひとつ納得がいきません。そこでinetdの起動にかえたところ納得できる結果になりました。

pure-ftpdの設定

/etc/inetd.conf

ftp     stream  tcp     nowait  root    /usr/sbin/pure-ftpd -O stats:/var/log/pure-ftpd/pureftpd.log       pure-ftpd -l puredb:/etc/pure-ftpd/pureftpd.pdb

/etc/default/pure-ftpd-common

STANDALONE_OR_INETD=inetd

/etc/pure-ftpd/pure-ftpd.conf

ChrootEveryone               yes
Daemonize                    no
NoAnonymous                  yes
PureDB                       /etc/pure-ftpd/pureftpd.pdb
MinUID                       100

ホームディレクトリのないユーザの作り方は検索で調べて次のようにしました。

groupadd ftpgroup -g 1123
useradd ftpuser -u 1122 -g ftpgroup -d /dev/null -s /etc

/etc/passwd

ftpuser:x:1122:1123::/dev/null:/etc

/etc/group

ftpgroup:x:1123:

pure-ftp専用のパスワード作成

sudo pure-pw useradd vftp -m -u 1122 -g 1123 -d /srv/ftp
sudo pure-pw mkdb

ユーザの削除

pure-pw userdel someuser

ユーザの確認

pure-pw list
pure-pw show someuser

port: 21/tcp

Note: ftpのモードにはアプティブモードとパッシブモードがあります。VPN経由でFirewallの状態によってはパッシブモードが使えない場合があります。

rsyncの設定

サーバ側:

/etc/default/rsync

RSYNC_ENABLE=true

/etc/inetd.conf

rsync stream tcp nowait root /usr/bin/rsync rsyncd --daemon

/etc/rsyncd.conf

charset = utf-8
uid = root
gid = root
use chroot = no
#use chroot = yes
log file = /var/log/rsyncd.log
pid file = /var/run/rsyncd.pid
read only = yes
auth users = root
secrets file = /etc/rsyncd.secret

[etc]
path = /etc
read only = no
uid root
gid root

[var]
path = /var
read only = no
uid root
gid root

/etc/rsyncd.secret

root:<password>

port: 873/tcp

クライアント側:

export RSYNC_PASSWORD=<password>
rsync -a -v --delete rsync://root@<server IP>/etc $dir/etc
rsync -a -v --delete rsync://root@<server IP>/var $dir/var